package com.wwdx.controller.base;

import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.util.SignUtils;
import com.google.common.collect.Maps;
import com.wwdx.model.MerchantOrderModel;
import com.wwdx.model.dto.BasePayDTO;
import com.wwdx.model.dto.ResultBean;
import com.wwdx.service.impl.BaseWxPayService;
import com.wwdx.utils.IpUtils;
import com.wwdx.utils.math.BigDecimalUtils;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.web.bind.annotation.RequestMapping;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;

/**
 * @author liguoliang
 */
public abstract class BaseWxPayController extends BaseController{

    /**
     * 获取子类service
     * @return BaseWxPayService
     */
    protected abstract BaseWxPayService getPayService();

    /**
     * 请求路径名称
     * @return 子类请求路径名称
     */
    protected abstract String getPayName();

    /**
     * 统一支付
     */
    @RequestMapping("/save")
    @ApiOperation(value = "微信公众号支付",httpMethod = "POST")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "token",value = "令牌",required = true),
            @ApiImplicitParam(name = "sign",value = "签名",required = true),
            @ApiImplicitParam(name = "timestamp",value = "时间戳",required = true),
            @ApiImplicitParam(name = "payOpenId",value = "支付ID",required = true),
            @ApiImplicitParam(name = "merchantId",value = "分销商ID",required = true),
            @ApiImplicitParam(name = "productId",value = "产品ID",required = true),
            @ApiImplicitParam(name = "type",value = "产品类型 1精品课 2书籍 3阅读币 4小课 7会员",required = true)
    })
    public ResultBean save(String token, String sign, String timestamp,String merchantId,String payOpenId,
                             Long productId, Integer type, HttpServletRequest request) {
        SortedMap<String, Object> sortedMap = super.getSortedMap(token,timestamp);
        sortedMap.put("productId",productId);
        sortedMap.put("type",type);
        sortedMap.put("merchantId",merchantId);
        ResultBean resultBean = super.validSignTokenResult(sign,sortedMap);
        if (resultBean.getStatus() == 1){
            return resultBean;
        }
        String openId = (String) resultBean.getData();
        if (StringUtils.isBlank(payOpenId)){
            payOpenId = openId;
        }
        MerchantOrderModel model = MerchantOrderModel.builder().type(type).orderNo(super.makeOrderNum())
                .productId(productId).payOpenId(payOpenId).openId(openId).merchantId(merchantId).build();
        logger.info("微信H5支付:{}",model);
        BasePayDTO.PayModel payModel = payService.save(model);
        if (StringUtils.isNotBlank(payModel.getError())) {
            return renderError(payModel.getError());
        }
        return this.execute(request,model,payModel,WxPayConstants.TradeType.JSAPI);
    }

    private ResultBean execute(HttpServletRequest request, MerchantOrderModel model, BasePayDTO.PayModel payModel, String payType){
        Integer totalFee = BigDecimalUtils.calculate(payModel.getProductPrice()).multiply(100).getBigDecimal().intValue();
        String notifyUrl = domain + getPayName() + "/wxcallback";
        //统一支付
        WxPayUnifiedOrderRequest prepayInfo = WxPayUnifiedOrderRequest.newBuilder()
                .openid(model.getPayOpenId()).outTradeNo(payModel.getOrderno())
                .totalFee(totalFee).body(payModel.getProductName())
                .tradeType(payType).spbillCreateIp(IpUtils.getIpAddr(request))
                .notifyUrl(notifyUrl).build();
        try {
            return renderSuccess(this.getPayService().createOrder(prepayInfo));
        } catch (WxPayException e) {
            this.logger.error(e.getErrCodeDes());
            return renderError(e.getErrCodeDes());
        }
    }

    /**
     * 微信支付回调处理
     */
    @RequestMapping(value = "/wxcallback")
    @ApiIgnore
    public synchronized void wxcallback(HttpServletRequest request, HttpServletResponse response) {
        try {
            Map<String, String> kvm = this.parseRequestXmlToMap(request);
            String result = "SUCCESS";
            boolean isResult = SignUtils.checkSign(kvm, WxPayConstants.SignType.MD5,this.getPayService().getConfig().getMchKey());
            String resultCode = kvm.get("result_code");
            if (isResult && result.equals(resultCode)) {
                //回调处理
                BasePayDTO.PayCallBackDTO dto = new BasePayDTO.PayCallBackDTO();
                //商户订单号
                String outTradeNo = kvm.get("out_trade_no");
                dto.setTradeNo(outTradeNo);
                //异步处理订单业务信息
                taskExecutor.execute(()-> payService.payBackHandler(dto));
                logger.info("out_trade_no: " + outTradeNo + " pay SUCCESS!");
                response.getWriter().write(this.setXML("SUCCESS", "OK"));
            } else {
                this.logger.error("out_trade_no: " + kvm.get("out_trade_no") + " check signature FAIL");
                response.getWriter().write(this.setXML("FAIL", "check signature FAIL"));
            }
        } catch (IOException e) {
            try {
                response.getWriter().write(this.setXML("FAIL", "result_code is FAIL"));
                logger.error("xml parse error,{}", e.getMessage());
            }catch (IOException ex){
                logger.error(ex.getMessage());
            }
        }
    }

    /**
     * 将微信服务器发送的Request请求中Body的XML解析为Map
     * @param request HttpServletRequest
     * @return map
     */
    private Map<String, String> parseRequestXmlToMap(HttpServletRequest request) {
        // 解析结果存储在HashMap中
        Map<String, String> resultMap = Maps.newHashMapWithExpectedSize(16);
        //读取输入流
        try (InputStream inputStream = request.getInputStream()){
            SAXReader reader = new SAXReader();
            Document document = reader.read(inputStream);
            //得到xml根元素
            Element root = document.getRootElement();
            // 得到根元素的所有子节点
            List<Element> elementList = root.elements();
            //遍历所有子节点
            for (Element e : elementList) {
                resultMap.put(e.getName(), e.getText());
            }
        } catch (DocumentException | IOException e) {
            logger.error("错误信息：{}",e.getMessage());
        }
        return resultMap;
    }

    /**
     * 微信消息回复格式
     * @param returnCode 返回状态码
     * @param returnMsg 返回消息
     * @return 指定格式内容
     */
    private String setXML(String returnCode, String returnMsg) {
        return "<xml><return_code><![CDATA[" + returnCode + "]]></return_code><return_msg><![CDATA[" + returnMsg + "]]></return_msg></xml>";
    }
}